home *** CD-ROM | disk | FTP | other *** search
/ Developer CD Series 1999 May: Tool Chest / Developer CD Series Tool Chest (Apple Computer)(May 1999).iso / Tool Chest / Games / Game Sample Code / ZAM 1.0a13 / GameSource / GameSounds.c < prev    next >
Encoding:
C/C++ Source or Header  |  1993-09-16  |  9.4 KB  |  331 lines  |  [TEXT/KAHL]

  1. /*
  2.     This file manages the asynchronous sounds used by the game.
  3.     It demonstrates one of the simplest techniques of playing asynch sounds,
  4.     using SndPlay with a callback.  Notice this code does NOT depend on register a5
  5.     for flagging that a sound is done, instead I stuff a pointer to the flag
  6.     in the callback parameters.  I am pointing this out because I think it is a much better
  7.     way to do things, and the same technique can apply to any interrupt time code.
  8.     
  9.     This also manages each channel by using a channel priority.  When a sound play request
  10.     is received for a specific channel, if the sound has a higher priority it is played,
  11.     otherwise it is ignored.
  12.     
  13.     This code is pretty re-usable,  I have used it in a few other programs as well.
  14.     
  15.     Call InitSounds( filename )  to start it up.  The file passed is a resource file
  16.     that contains all the snds you need.
  17.     
  18.     Then use PlaySndAsynchChannel to play a sound, specifying the sound resource ID, the channel
  19.     number, and the priorty.  Right now this handles kNumChannels of sound.
  20.     
  21.     Call SoundKeeper() from your idle routine to make sure that the sound channels are
  22.     disposed of when needed.
  23.     
  24.     Call FreeSounds before your program quits.  Thats really the minimum you need in order
  25.     to use this code.  There is more to it, read the code and comments that follow.
  26.     
  27.     
  28.     
  29.     A point to mention here.  There are so many different Macintosh sound managers out there,
  30.     that it is hard to write good code that runs on all of them.  This code SHOULD work on all
  31.     of them.  If not, please let me know.  See the feedback forms.
  32.     
  33.     This code has two forks in it.  If Sound Manager 3.0 is running, then it creates the
  34.     sound channels at the start of the program, and keeps using them until the program quits.
  35.     Earlier versions of the Sound Manager contained bugs, which caused intermittent failures
  36.     unless a fresh sound channel was used for each asynchronous sound.  So, if a sound manager
  37.     earlier than 3.0 is installed, then this unit will create and dispose of sound channels
  38.     on the fly.
  39.     
  40.     UNDER Sound Manager 3.0 on an AV macintosh with a DSP, it is important to open the channel
  41.     once and keep using it until you are done.  This is because every time the channel is opened
  42.     (or closed) the disk may be hit as the DSP Synth code is loaded.
  43.     
  44.     Creating and disposing of a sound channel is not the most efficient way to play sounds, because the channel is created and
  45.     disposed of for each sound, requiring the memory manager to scam a block from the heap.
  46.  
  47.     It would be better to open a channel, leave it open, and keep jamming bufferCmds in to 
  48.     play each sound instead.  However - this does not work on all macs, like MacClassics, plusses
  49.     and some others.  If Sound Manager 3.0 is installed, then this code will work well.
  50.  
  51.     I have made this the simplest way of playing asynchronous sounds, but it is NOT the best for 
  52.     each particular Macintosh.
  53.  
  54.     Double buffering is good because it can use less RAM, but it is bad because if you are spooling
  55.     the sound in then you are reading the disk or custom un-compressing, or otherwise doing
  56.     some amount of processing to fill the buffer, and this can slow the program down, not to mention
  57.     increase the complexity of any sound playing code.  
  58.     
  59. */
  60.  
  61. #include "GameSounds.h"
  62.  
  63. #include "ZAMProtos.h"
  64.  
  65. unsigned char    gSoundMgrVersion;
  66. SndChanInfo        gChan[kNumChan];
  67. short            gSndResFile;
  68. Boolean            gSoundEnable;
  69.  
  70. void SoundEnable(void);
  71.  
  72. void InitSounds(Str255    sndFileName)
  73. /*
  74.     open up the sound resource file, and preserve the resref num and stuff
  75.     
  76. */
  77. {
  78.     short    i;
  79.     OSErr    err;
  80.     short    prevResFile;
  81.     
  82.     
  83.     SoundEnable();
  84.      
  85.     /* get the version number of the sound manager */
  86.     gSoundMgrVersion = SndSoundManagerVersion().majorRev;
  87.  
  88.     for(i = 0; i < kNumChan; i++) {
  89.         gChan[i].channel = nil;
  90.         gChan[i].priority = 0;
  91.  
  92.         if(gSoundMgrVersion >= 3) {
  93.             /* we can pre-allocate the channels and leave them open till we quit */
  94.             err = SndNewChannel(&gChan[i].channel, sampledSynth, 
  95.                             initMono + initNoDrop + initNoInterp, SndDoneProc);
  96.             if(err) {
  97.                 ErrMsgCode("\pError opening sound channel");
  98.                 ExitToShell();
  99.             }
  100.         }
  101.     }
  102.     
  103.     /* now open the file with our sounds in it */
  104.     prevResFile = CurResFile();    
  105.     gSndResFile = OpenResFile(sndFileName);
  106.     if(gSndResFile == -1) {
  107.         ErrMsg("\pError opening sound resource file");
  108.     }
  109.     
  110.     /* remove the sound file from chain */
  111.     UseResFile(prevResFile);
  112. }
  113.  
  114.  
  115. void SoundDisable(void)
  116. /*
  117.     turn sounds off
  118.     
  119. */
  120. {
  121.     gSoundEnable = false;
  122. }
  123.  
  124. void SoundEnable(void)
  125. /*
  126.     turn sounds on
  127.     
  128. */
  129. {
  130.     gSoundEnable = true;
  131. }
  132.  
  133. void StopAllSounds(void)
  134. /*
  135.     Stop all sounds from playing by dumping them
  136. */
  137. {
  138.     short    i;
  139.     
  140.     for(i = 0; i < kNumChan; i++) {
  141.         if(gChan[i].priority)
  142.             SndDisposeChannel (gChan[i].channel, true);
  143.     }
  144. }
  145.  
  146.  
  147. Handle    GetSound(short    sndID)
  148. /*
  149.     Use this to get sounds from the file
  150.     This makes the sound res file current again
  151.     and grabs the sound.
  152.     
  153.     Inside Mac is ambiguous about locking down sound handles.
  154.     
  155.     They MUST be locked down before passed to sound play.
  156. */
  157. {
  158.     short    saveResFile;
  159.     Handle    snd;
  160.     
  161.     saveResFile = CurResFile();
  162.  
  163.     UseResFile(gSndResFile);
  164.     snd = Get1Resource('snd ',sndID);
  165.     HLock(snd);
  166.     UseResFile(saveResFile);
  167.  
  168.     return snd;
  169. }
  170.  
  171. OSErr PlaySndAsynchChannel(short sndID, short chanNum, short priority)
  172. /*
  173.     This is the main sampled sound playing routine.  See comments above.
  174.     
  175. */
  176. {
  177.     OSErr            err = noErr;
  178.     SndCommand        cmd;
  179.     Handle            snd;
  180.     
  181.     if (!gSoundEnable)    return;
  182.     
  183.     snd = GetSound(sndID);
  184.     if(snd != nil) {
  185.         if( (gChan[chanNum].priority != 0)  && (priority >= gChan[chanNum].priority) ) {
  186.             /* if we got the sound and the channel is busy, and our priority is high enough */
  187.             /* then we are going to play, otherwise we bail */
  188.             if(gChan[chanNum].priority) {
  189.                 if(gSoundMgrVersion < 0x03) {
  190.                     SndDisposeChannel (gChan[chanNum].channel, true);
  191.                 } else {
  192.                     /* since the good sound manager is around, stop playing the sound */
  193.                     cmd.cmd = quietCmd;
  194.                     cmd.param1 = 0;
  195.                     cmd.param2 = 0;
  196.                     err = SndDoImmediate(gChan[chanNum].channel, &cmd);
  197.                     cmd.cmd = flushCmd;
  198.                     err = SndDoImmediate(gChan[chanNum].channel, &cmd);
  199.                 }
  200.             }
  201.         }
  202.  
  203.         if(priority >= gChan[chanNum].priority) {
  204.             if( gSoundMgrVersion < 0x03) {
  205.                 /* old sound manager around, so create a new sound channel */
  206.                 gChan[chanNum].channel = nil;
  207.                 err = SndNewChannel(&gChan[chanNum].channel, sampledSynth, initMono + initNoDrop + initNoInterp, SndDoneProc);
  208.             }
  209.             if(err == noErr) {
  210.                 err = SndPlay (gChan[chanNum].channel, snd, true);
  211.                 if (err == noErr) {
  212.                     gChan[chanNum].sndHandle = snd;
  213.                     gChan[chanNum].priority = priority;
  214.                     cmd.cmd = callBackCmd;
  215.                     cmd.param2 = (long)&gChan[chanNum];
  216.                     err = SndDoCommand (gChan[chanNum].channel, &cmd, false);
  217.                 }
  218.             }
  219.         }
  220.     }
  221.     
  222.     return err;
  223. }
  224.  
  225.  
  226. OSErr PlaySndAsynchChannelNow(short sndID, short chanNum, short priority)
  227. /*
  228.     The same as above, except does not check if sounds are enabled.
  229.     This was a hacky way to make sure that you could hear the sorry bub you loose sound.
  230. */
  231. {
  232.     OSErr            err = noErr;
  233.     SndCommand        cmd;
  234.     Handle            snd;
  235.     
  236.     snd = GetSound(sndID);
  237.     if(snd != nil) {
  238.         if( (gChan[chanNum].priority != 0)  && (priority >= gChan[chanNum].priority) ) {
  239.             /* if we got the sound and the channel is busy, and our priority is high enough */
  240.             /* then we are going to play, otherwise we bail */
  241.             if(gChan[chanNum].priority) {
  242.                 if(gSoundMgrVersion < 0x03) {
  243.                     SndDisposeChannel (gChan[chanNum].channel, true);
  244.                 } else {
  245.                     /* since the good sound manager is around, stop playing the sound */
  246.                     cmd.cmd = quietCmd;
  247.                     cmd.param1 = 0;
  248.                     cmd.param2 = 0;
  249.                     err = SndDoImmediate(gChan[chanNum].channel, &cmd);
  250.                     cmd.cmd = flushCmd;
  251.                     err = SndDoImmediate(gChan[chanNum].channel, &cmd);
  252.                 }
  253.             }
  254.         }
  255.  
  256.         if(priority >= gChan[chanNum].priority) {
  257.             if( gSoundMgrVersion < 0x03) {
  258.                 /* old sound manager around, so create a new sound channel */
  259.                 gChan[chanNum].channel = nil;
  260.                 err = SndNewChannel(&gChan[chanNum].channel, sampledSynth, initMono + initNoDrop + initNoInterp, SndDoneProc);
  261.             }
  262.             if(err == noErr) {
  263.                 err = SndPlay (gChan[chanNum].channel, snd, true);
  264.                 if (err == noErr) {
  265.                     gChan[chanNum].sndHandle = snd;
  266.                     gChan[chanNum].priority = priority;
  267.                     cmd.cmd = callBackCmd;
  268.                     cmd.param2 = (long)&gChan[chanNum];
  269.                     err = SndDoCommand (gChan[chanNum].channel, &cmd, false);
  270.                 }
  271.             }
  272.         }
  273.     }
  274.     
  275.     return err;
  276. }
  277.  
  278. void SoundKeeper(void)
  279. /*
  280.     This routine must be called from your idle loop.
  281.     It updates the sound channels, getting rid of them and setting the flags correctly.
  282. */
  283. {
  284.     short    i;
  285.     
  286.     for(i = 0; i < kNumChan; i++) {
  287.         if(gChan[i].priority == -1) {
  288.             gChan[i].priority = 0;
  289.             if(gChan[i].sndHandle)
  290.                 HUnlock(gChan[i].sndHandle);    /* do you really want to have a big old unlocked block?*/
  291.  
  292.             if(gSoundMgrVersion < 0x03) {
  293.                 SndDisposeChannel (gChan[i].channel, true);
  294.             }
  295.             
  296.         }
  297.     }
  298. }
  299.  
  300. pascal void SndDoneProc(SndChannelPtr channel, SndCommand *cmd)
  301. /*
  302.     This is the magical callback routine that flags SoundKeeper to dump this channel.
  303.     It is magic because it does not use register a5, like most people suggest.
  304.     I think it is lame to use register a5 to access a global from routines like this.
  305.     It is much better to just store a pointer to the data you need!
  306. */
  307. {
  308.     SndChanInfo    *sndChan;
  309.  
  310.     sndChan = (SndChanInfo*)cmd->param2;
  311.     sndChan->priority = -1;
  312. }
  313.  
  314. void FreeSounds(void)
  315. /*
  316.     Disposes of all the currently allocated sound channels
  317.     and stops all sounds from playing.
  318.     Also closes the sound file
  319. */
  320. {
  321.     short    i;
  322.     
  323.     for(i = 0; i < kNumChan; i++) {
  324.         SndDisposeChannel (gChan[i].channel, true);
  325.         gChan[i].priority = 0;
  326.     }
  327.     
  328.     if(gSndResFile != -1) {
  329.         CloseResFile(gSndResFile);
  330.     }
  331. }